/**
 * Auteur : Dominique GUERIN
 * dominiquephDOTguerinATgmailDOT..
 * dominiqueDOTguerinATinseeDOTfr(ance)  
 * Remerciements: � Keith Brown pour ses deux livres sur la s�curit� sous Windows et ses diff�rents articles
 *                 et � Michel Barnett de Microsoft pour ses deux articles 
 *                ".NET Remoting Authentication and Authorization Sample"  et le code qui les accompagne
 *  Le code est utilisable, modifiable et redistribuable � volont� sous la condition de ne pas supprimer ces 7 lignes.
 */ 

package fr.doume.cli.sspi;


//tomcat 5.5
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

//tomcat 6
//import org.apache.juli.logging.Log;
//import org.apache.juli.logging.LogFactory;

import fr.doume.cli.sspi.SSPException;
import fr.doume.cli.sspi.TranslationRolesIntoSids;

import java.io.ByteArrayOutputStream;

import fr.doume.cli.authenticator.Parameters;
import fr.doume.cli.authenticator.AuthenticationContext;


import java.io.InputStream;
import java.io.OutputStream;
import java.util.Arrays;


import java.io.DataInputStream;
import java.io.DataOutputStream;

import java.io.IOException;

import java.util.Timer;
import java.util.TimerTask;

import java.util.Date;
import fr.doume.cli.Jvm2Cli;


/**
* Wraps  the exchanges with NegoServer .
  */
public class SSPAuthentication
{
	private static Log log = LogFactory.getLog(SSPAuthentication.class);
	

 /**
  *  State send by NegotiateStream.<br>
  * <ol>Three states : 
  * <li> ERROR (not authenticated) (2),
  * <li> 0 ( authenticated ), 
  * <li> CONTINUE_NEEDED (1) (another exchange bettween the client and the server is required, NTLM).
  * </ol>
  */ 
	//private static final int CONTINUE_NEEDED = 1;
	//private static final int ERROR = 2;
	//private static final int NOTAUTHENTICATEDUSER = 3;

  /**
   * Time-out used by the connection and the calls to Read and Write of the socket
   */
  private static final int TIME_OUT_MS =8000;

  /**
   * Status code send by NegotioateStream in Negoserver ( [MS-NSS].pdf )
   */
  private static final byte HANDSHAKEDONE= 0x14;

    /**
    * Status code send by NegotioateStream in Negoserver ( [MS-NSS].pdf )
   */
  private static final byte HANDSHAKEERROR= 0x15;

    /**
   * Status code send by NegotioateStream in Negoserver ( [MS-NSS].pdf )
   */
  private static final byte HANDSHAKEINPROGRESS= 0x16;

  private static Object sync = new Object();
	/**
	* status code send by NegoServer
	*/
	private long failed;
	/**
	* Is NegotiateStream waiting other bytes ?
	*/
	private long continue_needed;
	
	private boolean isContextSet;
	
	private boolean existUserName;	
	private String userName = null;
  /**
  * Name of the Security Service Provider(NTLM or Kerberos)
  */
  private String SSP = null;
	private boolean existSSP;

  /**
   * True if the method communicateWithNegoServer has been already called (If yes => NTLM)
   */
	private boolean already_called;
	
	/**
	* Instance given to the constructor by the AuthenticationContext
  * This instance has the Sids of the roles of tomcat.
	*/ 
	private TranslationRolesIntoSids translationTomcatRolesiIntoSids;
	
	private AuthenticationContext authenticationContext;

	private Parameters param;
	
	/**
	*Sids from de translationTomcatRolesiIntoSids
	*/
	private byte[][] sids;
  /**
  * Sent by the the NegoServer service. If a SID is in the access token of the client, 
  * the element of the array is set to 1, otherwise to 0 
  */
	//private byte[] groupsInAccessToken;
	private boolean[] groupsInAccessToken;
	
	/**
	 * Socket to the connection to NegoServer. Can be reused , when NTLM is the Security Service Privider.
	 */ 
  private Timer stop = null;
  
  private long handle;

  
  //private CancelConnectionForNTLM timeLifeSocketForNtlm = null;
	
    public SSPAuthentication(AuthenticationContext ac){
		failed = 0;
		continue_needed = 0;
		isContextSet = false;
		existUserName = false;
		existSSP = false;
		already_called = false;
		authenticationContext = ac;
		translationTomcatRolesiIntoSids = ac.getTranslationRolesIntoSids();
		param = ac.getParameters();
		groupsInAccessToken = new boolean[0];
		log.debug("**********************************************New SSPAuthentification!!!");
	}
	/**
	* Receives the bytes sent by the client et returns bytes to send to the client.
	* Calls the method communicateWithNegoServer.
	*/ 
	public byte[] acceptSecContext( byte[] token)
	throws SSPException
	{
		if (isContextSet){
			throw new SSPException("The SSPI/GSSAPIcontext is already established ");
		}
		if(token == null){
      throw new SSPException("The array sent by the client is null");
    }
		if (token.length == 0){
			throw new SSPException("The length of the array sent by the client is 0");
		}
		if ( failed != 0){
				throw new SSPException("SSPAuthentification already in error");
		}

		String msg = "";
		String user = "";
		String l_SSP = "";

		byte[] tokenToClient =  null;
    
    int sidsCount = 0;

		if (! already_called )
		{
      if ( param.areGroupsInAd() )
      {
        try
        {
          //if ( param.areGroupsInAd())
          {
            sidsCount = translationTomcatRolesiIntoSids.getSidsCount();
            if (log.isDebugEnabled()) log.debug("Count of sids given by TraduireNomsEnSids : " + sidsCount);
          }
        }
        catch (SSPException e)
        {
          if (log.isDebugEnabled()) log.debug("Count of Tomcat's roles is 0 : " +e.getMessage());
        }
      }
    }
    
    //if (timeLifeSocketForNtlm != null) timeLifeSocketForNtlm.cancel();
    
    byte [][] reftokenToClient= new byte[1][]; reftokenToClient[0] = null;

    long[]handle_out = new long[1]; handle_out[0] = 0;
    String[] username_out = new String[1]; username_out[0] = "";
    String[] SSP_out = new String[1]; SSP_out[0] = "";
    boolean[][] groupsInAccessToken_out = new boolean[1][]; groupsInAccessToken_out[0] = null;
		sids = translationTomcatRolesiIntoSids.getSids();



    if (log.isDebugEnabled())
    {
      log.debug("Before Accept_sec" );
    }
    
    long handle_in = 0;
    if (handle != 0)
    {
      synchronized(sync)
      {
        handle_in = handle;
        handle = 0;
      }
    }
    //int status = communicateWithNegoServer(token, reftokenToClient);
    int status = Accept_sec( token, reftokenToClient, handle_in, handle_out, username_out, sids, groupsInAccessToken_out, SSP_out);

    if (log.isDebugEnabled())
    {
      log.debug("After Acceptsec" );
    }



    tokenToClient = reftokenToClient[0];
    
    l_SSP ="OUF!";
    user ="DOUME";
    already_called = true;
    msg = "Error between tomcat and NegotiateStream!";

    if (log.isDebugEnabled())
    {
      if (tokenToClient == null) log.debug("Cont of bytes of tokenToClient : 0");
      else log.debug("Cont of bytes of tokenToClient : " +tokenToClient.length );
    }
    
    if (status == HANDSHAKEDONE){ //Contexte etabli
			failed = 0;
			continue_needed = 0;
			handle = 0;
			isContextSet = true;
			existUserName = true;
			userName = username_out[0];
			if (userName == null) userName = user;
			existSSP = true;
			SSP = SSP_out[0];
			if (SSP == null) SSP = l_SSP;
      if ( param.areGroupsInAd())
      {
        groupsInAccessToken = groupsInAccessToken_out[0];
        if (log.isDebugEnabled())
        {        
          log.debug("Count of Sids translated by translationTomcatRolesiIntoSids : " + translationTomcatRolesiIntoSids.getSidsCount());  
          if (groupsInAccessToken != null && groupsInAccessToken.length != 0)
          {
            for (int i = 0; i < groupsInAccessToken.length; i++)
            {
              log.debug(" group number " + i + " = " + groupsInAccessToken[i] ); // + " (1 if in the access token, otherwise 0)");
            }
          }
          else
          {
            log.debug("the length of groupsInAccessToken is  0");
          }
        }
			}
		} //status == HANDSHAKEDONE)
		else if (status == HANDSHAKEINPROGRESS)
		{//continue_needed
			failed = 0;
			continue_needed = 1;
			handle = handle_out[0];
			isContextSet = false;
			existUserName = false;
			existSSP = false;
			//timeLifeSocketForNtlm = new CancelConnectionForNTLM();
		}
		
		else
		{//Erreur
			failed = 1;
			continue_needed = 0;
			handle = 0;
			isContextSet = false;
			already_called = false;
			existUserName = false;
			existSSP = false;
			if (log.isDebugEnabled()) log.debug("Error in AcceptSecContext : User is not authenticated");
			throw new SSPException("Error in AcceptSecContext : User is not authenticated");
		}

		return tokenToClient;
	}

	/**
	* Is client authentification estabblished ?
	*/
	public boolean isEstablished()
	{
		if (isContextSet){
			return true;
		}else{
			return false;
		}
	}
	
	/**
	* Is client authentification failed ?
	*/
	public boolean isFailed()
	{
    if (failed == 0) return false;
    else return true;
	}
	
  /**
  * Returns the name of the client when the authentication is established
  */
	public String getUserName()	
	throws SSPException
	{
		if (existUserName){
			return userName;
		}
		else{
			throw new SSPException("User's name is unknown");
		}
	}
  /** Returns the Security Service Provider used by the authentication 
  * (NTLM or Kerberos) . Only for logs
  */
	public String getSSP()	
	throws SSPException
	{
		if (existSSP)
		{
			return SSP;
		}
		else
		{
			throw new SSPException("The name of the Security Service Provider Interface is unknown");
		}
	}

  /**
  * Returns a array of bytes given by Negoserver. 
  */
	public boolean[] areRolesInAccessToken()
	{
		//Todo verification
		return groupsInAccessToken;
	}

  
  public void login (String Name, String password)
  throws SSPException
  {
    String user ="DOUME";

    String[] username_out = new String[1]; username_out[0] = "";
    boolean[][] groupsInAccessToken_out = new boolean[1][]; groupsInAccessToken_out[0] = null;
		sids = translationTomcatRolesiIntoSids.getSids();

    int status = dologin(Name, password, username_out, sids, groupsInAccessToken_out);
    
    if (status == HANDSHAKEDONE){ //Contexte etabli
			failed = 0;
			continue_needed = 0;
			isContextSet = true;
			existUserName = true;
			userName = username_out[0];
			if (userName == null) userName = user;
			if (log.isDebugEnabled())  log.debug(" userName : " + userName);

			existSSP = true;
			SSP = "NTLM";
      if ( param.areGroupsInAd())
      {
        groupsInAccessToken = groupsInAccessToken_out[0];
        if (log.isDebugEnabled())
        {          
          log.debug("Count of Sids translated by translationTomcatRolesiIntoSids : " + translationTomcatRolesiIntoSids.getSidsCount());  
          if (groupsInAccessToken.length != 0)
          {
            for (int i = 0; i < groupsInAccessToken.length; i++)
            {
              log.debug(" group number " + i + " = " + groupsInAccessToken[i] ); // + " (1 if in the access token, otherwise 0)");
            }
          }
          else
          {
            log.debug("the length of groupsInAccessToken is  0");
          }
        }
			}
		} //status == HANDSHAKEDONE)
		else
		{//Erreur
			failed = 1;
			continue_needed = 0;
			isContextSet = false;
			already_called = false;
			existUserName = false;
			existSSP = false;
			if (log.isDebugEnabled()) log.debug("Error in AcceptSecContext : User is not authenticated");
			throw new SSPException("Error in AcceptSecContext : User is not authenticated");
		}
      
      
  }

  

  private int Accept_sec(byte[] token, byte[][] token_out, long handle, long[] handle_out, String[] name_out, 
                byte [][] sids, boolean[][] presents_out, String[] ssp_out)
  {
    //return 0;
    return Jvm2Cli.Accept_sec(token, token_out, handle, handle_out, name_out, sids, presents_out, ssp_out);
  }
  
  private int dologin(String userName, String password, String[] username_out, byte[][] sids, boolean[][] groupsInAccessToken_out)
  {
    return Jvm2Cli.Login(userName, password, username_out, sids, groupsInAccessToken_out);
  }

	protected void finalize()
	{
		if (handle != 0) 
		{
			Jvm2Cli.FreeAuthenticator(handle);
		}
	}


  /**
  * Wraps a class <c>ReleaseCliAuthenticator</c>: 
  * Get a hanfle to a Cli Authenticator and defines a time out to close this authenticator not reused to finish the authentication (NTLM)
  * Used by the class <c>SSPAuthentication</c>
  */

//class CancelConnectionForNTLM
//{
  //private Timer kill;
        //private static Log log = LogFactory.getLog(CancelConnectionForNTLM.class);
  
  //public CancelConnectionForNTLM()
  //{
    //kill = new Timer(); // arr�t de la socket au bout de 15 seconde...
    //kill.schedule( new ReleaseCliAuthenticator() , 15 * 1000);
    //if (log.isDebugEnabled()) log.debug("A new CancelConnectionForNTLM");

  //}
  /**
   * Cancel a timer
   */
  //public void cancel()
  //{
      //try
      //{
        //if (kill != null)
        //{
          //kill.cancel();
          //if (log.isDebugEnabled()) log.debug("A Timer for ConnectionForNTLM has been canceled");
        //}
      //}
      //catch(IllegalStateException e){}
      //catch(IllegalArgumentException e){}
      //finally
      //{
        //kill = null;
      //}
    //}
  
    /**
  * Defines the method run called by a timer.
  */

  //private class ReleaseCliAuthenticator extends TimerTask
  //{
   /**
   *  Closes the socket used by an NTLM authentication, if the authentication is not finished after a time out.
   */
    //public void run()
    //{
      //long handle_copy = 0;
      
      //synchronized(sync)
      //{
        //if (handle != 0)
        //{
          //handle_copy = handle;
          //handle = 0;
        //}
      //}
      //if (handle_copy != 0)
      //{
            //Jvm2Cli.FreeAuthenticator(handle_copy);
        //if (log.isDebugEnabled()) log.debug(" ReleaseCliAuthenticator");
      //}
      
    //}
    
  //}

//}

}
